//@author A0096759E

package storage;

import java.util.Scanner;

import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

public class ScheduleEvent implements ScheduleEventInterface {

	private static final String STRING_EMPTY = "";
	private static final int EVENT_TYPE_FLOATING = 0;
	private static final int EVENT_TYPE_DEADLINE = 1;
	private static final int EVENT_TYPE_TIMED = 2;
	
	private static final String FLOATING_TASK_DATE = "99999999";
	private static final String EVENT_TIME_END_MIDNIGHT = "2359";
	
	private static final String GOOGLE_TASK_ITEM_JSON_WITH_DATE = "\n'title': '%s', \n'due': '%s', \n'notes': '%s', \n'status': '%s' \n";
	private static final String GOOGLE_TASKS_COMPLETED = "completed";
	private static final String GOOGLE_TASKS_NEEDS_ACTION = "needsAction";

	private static final String DATETIME_FORMAT_LOCAL_DATETIME = "ddMMyyyyHHmmss";
	private static final String DATETIME_FORMAT_LOCAL_DATE = "ddMMyyyy";
	private static final String DATETIME_FORMAT_GCAL_DATE = "yyyy-MM-dd";
	private static final String DATETIME_FORMAT_GCAL_UPDATED = "%sT%sZ";
	private static final String DATETIME_FORMAT_GCAL_ITEM_DATETIME = "yyyy-MM-dd'T'HH:mm:ss'+08:00'";
	private static final String DATETIME_FORMAT_GTASK_UPDATED = "%sT%s.000Z";
	private static final String DATETIME_FORMAT_GTASK_ITEM_DATETIME = "yyyy-MM-dd'T00:00:00.000Z'";
	private static final String DATETIME_FORMAT_TIME_WITH_SECONDS = "HH:mm:ss";
	private static final String DATETIME_FORMAT_TO_STRING_DATE = "dd MMM yyyy";
	private static final int DATETIME_TIME_DIFFERENCE = -8;
	private static final int DATETIME_HOURS_IN_MILLISECONDS = 3600000;
	
	private static final String SUCCESS_MSG_MARKED_DONE = "Successfully marked %s [DONE]!";
	private static final String SUCCESS_MSG_MARKED_UNDONE = "Successfully marked %s [UNDONE]!";

	private static final String TO_STRING_EVENT_NAME = "Event: %s";
	private static final String TO_STRING_IS_DONE = " [DONE]";
	private static final String TO_STRING_IS_NOT_DONE = " [NOT DONE]";
	private static final String TO_STRING_DATE = "\nDate: %s";
	private static final String TO_STRING_START_TIME = "\nStart Time: %s";
	private static final String TO_STRING_END_TIME = "\nEnd Time: %s";
	
	private static final String TO_SAVE_JSON_EVENT_NAME = "{\r\n\t'eventName': '%s',\r\n";
	private static final String TO_SAVE_JSON_EVENT_TYPE = "\t'eventType': '%s',\r\n";
	private static final String TO_SAVE_JSON_EVENT_DATE = "\t'eventDate': '%s',\r\n";
	private static final String TO_SAVE_JSON_EVENT_UPDATED = "\t'updated': '%s',\r\n";
	private static final String TO_SAVE_JSON_EVENT_START = "\t'startTime': '%s',\r\n";
	private static final String TO_SAVE_JSON_EVENT_END = "\t'endTime': '%s',\r\n";
	private static final String TO_SAVE_JSON_EVENT_UID = "\t'UID': '%s',\r\n";
	private static final String TO_SAVE_JSON_EVENT_GOOGLEID = "\t'googleUID': '%s',\r\n";
	private static final String TO_SAVE_JSON_EVENT_ISDONE = "\t'isDone': '%s'\r\n}";

	private static final String TO_JSON_STARTING_BRACE = "{\n";
	private static final String TO_JSON_TIMED_EVENT_START = "%s,\n";
	private static final String TO_JSON_TIMED_EVENT_END = "%s";
	private static final String TO_JSON_TIMED_EVENT_SUMMARY = "'summary' : '%s',\n";
	private static final String TO_JSON_TIMED_EVENT_EXTENDED_ITEMS = "'extendedProperties': { \n'private': { \n'isDone': '%s', \n'localID': '%s', \n'eventType': '%d' \n} \n},\n";
	private static final String TO_JSON_DEADLINE_EVENT_NAME = "[by %s] %s";
	private static final String TO_JSON_FLOATING_EVENT_DETAILS = "\n'title': '%s', \n'notes': '%s', \n'status': '%s' \n";
	private static final String TO_JSON_EVENT_GOOGLEID = ",\n'id': '%s'";
	private static final String TO_JSON_ENDING_BRACE = "\n}";
	
	private static final String CREATE_TIME_ENTRY_TYPE_START = "start";
	private static final String CREATE_TIME_ENTRY_TYPE_END = "end";
	private static final String CREATE_TIME_ENTRY_OPENING = "'%s': { 'dateTime': '";
	private static final String CREATE_TIME_DATE_TIME_ENTRY = "%s%s00";
	private static final String CREATE_TIME_ENTRY_CLOSING = "'}";
	
	private int eventType;
	private String updated;
	private String eventName = null;
	private String eventDate = "99999999";
	private String startTime = null;
	private String endTime = null;
	private boolean isDone = false;
	private String UID = null;
	private String googleUID = null;
	
	Scanner sc = new Scanner(System.in);
	
	/**
	 * Constructor for a Google Downloaded Event
	 */
	public ScheduleEvent() {
	}
	

	/**
	 * Constructor for a Timed Event
	 * @param eventType
	 * @param eventName
	 * @param eventDate
	 * @param startTime
	 * @param endTime
	 */
	public ScheduleEvent(int eventType, String eventName, String eventDate, String startTime, String endTime) {
		this.eventName = eventName;
		this.eventType = eventType;
		this.eventDate = eventDate;
		this.startTime = startTime;
		this.endTime = endTime;
		this.isUpdated();
	}
	
	/**
	 * Constructor for a Deadline Event
	 * @param eventType
	 * @param eventName
	 * @param eventDate
	 * @param endTime
	 */
	public ScheduleEvent(int eventType, String eventName, String eventDate, String endTime) {
		this.eventName = eventName;
		this.eventType = eventType;
		this.eventDate = eventDate;
		this.endTime = endTime;
		this.isUpdated();
	}
	
	/**
	 * Constructor for a Floating Event
	 * @param eventType
	 * @param eventName
	 * @param eventDate
	 */
	public ScheduleEvent(int eventType, String eventName, String eventDate) {
		this.eventName = eventName;
		this.eventType = eventType;
		this.eventDate = eventDate;
		if (this.eventType == EVENT_TYPE_DEADLINE) {
			this.endTime = EVENT_TIME_END_MIDNIGHT;
		}
		this.isUpdated();
	}
	
	//Mutators
	
	/**
	 * Updates this event's update time to the current time
	 */
	public void isUpdated() {
		DateTime updateDateTime = new DateTime().withDurationAdded(DATETIME_HOURS_IN_MILLISECONDS, DATETIME_TIME_DIFFERENCE);
		DateTimeFormatter dateFormat = DateTimeFormat.forPattern(DATETIME_FORMAT_GCAL_DATE);
		DateTimeFormatter timeFormat = DateTimeFormat.forPattern(DATETIME_FORMAT_TIME_WITH_SECONDS);
		if (eventType == EVENT_TYPE_TIMED) {
			this.updated = String.format(DATETIME_FORMAT_GCAL_UPDATED, dateFormat.print(updateDateTime), timeFormat.print(updateDateTime));
		} else {
			this.updated = String.format(DATETIME_FORMAT_GTASK_UPDATED, dateFormat.print(updateDateTime), timeFormat.print(updateDateTime));
		}
	}
	
	/**
	 * Sets the updated time for this event to input time
	 * @param updated - time as string
	 */
	public void setUpdated(String updated) {
		this.updated = updated;
	}
	
	/**
	 * Sets the name of this event to input
	 * @param newEventName
	 */
	public void setName(String newEventName) {
		this.eventName = newEventName;
		this.isUpdated();
	}

	/**
	 * Sets the event type of this event to input
	 * @param newEventType
	 */
	public void setType(int newEventType) {
		this.eventType = newEventType;
		this.isUpdated();
	}

	/**
	 * Sets the date of this event to input
	 * @param newDate
	 */
	public void setDate(String newDate) {
		this.eventDate = newDate;
		this.isUpdated();
	}
	
	/**
	 * Sets the start time of this event to input
	 * @param newStartTime
	 */
	public void setStartTime(String newStartTime) {
		this.startTime = newStartTime;
		this.isUpdated();
	}

	/**
	 * Sets the end time of this event to input
	 * @param newEndTime
	 */
	public void setEndTime(String newEndTime) {
		this.endTime = newEndTime;
		this.isUpdated();
	}
	
	/**
	 * Sets this event as Done
	 * @return successMessage
	 */
	public String setIsDone() {
		this.isDone = true;
		this.isUpdated();
		return String.format(SUCCESS_MSG_MARKED_DONE, eventName);
	}
	
	/**
	 * Sets this event as Not Done
	 * @return successMessage
	 */
	public String setNotDone() {
		this.isDone = false;
		this.isUpdated();
		return String.format(SUCCESS_MSG_MARKED_UNDONE, eventName);
	}
	
	/**
	 * Sets the local UID of this event to input
	 * @param UID
	 */
	public void setUID(String UID) {
		this.UID = UID;
		this.isUpdated();
	}
	
	/**
	 * Sets the google UID of this event to input
	 * @param googleUID
	 */
	public void setGoogleUID(String googleUID) {
		this.googleUID = googleUID;
		this.isUpdated();
	}
	
	//Assessors
	
	/**
	 * Returns the name of this event
	 * @return eventName
	 */
	public String getName() {
		return this.eventName;
	}

	/**
	 * Returns the type of this event
	 * @return eventType
	 */
	public int getType() {
		return this.eventType;
	}

	/**
	 * Returns the date of this event
	 * @return eventDate
	 */
	public String getDate() {
		return this.eventDate;
	}

	/**
	 * Returns the start time of this event
	 * @return startTime
	 */
	public String getStartTime() {
		return this.startTime;
	}

	/**
	 * Returns the end time of this event
	 * @return endTime
	 */
	public String getEndTime() {
		return this.endTime;
	}
	
	/**
	 * Returns whether this event is done
	 * @return eventName
	 */
	public boolean getIsDone() {
		return this.isDone;
	}
	
	/**
	 * Returns the local UID of this event
	 * @return UID
	 */
	public String getUID() {
		return this.UID;
	}
	
	/**
	 * Returns the google UID of this event
	 * @return googleUID
	 */
	public String getGoogleUID() {
		return this.googleUID;
	}
	
	/**
	 * Returns the updated time of this event
	 * @return updated
	 */
	public String getUpdated() {
		return this.updated;
	}
	
	@Override
	public String toString() {
		StringBuilder stringToReturn = new StringBuilder();
		stringToReturn.append(onlyNameString());
		
		//This is to differentiate
		if (!eventDate.equals(FLOATING_TASK_DATE)) {
			DateTimeFormatter savedDateFormat = DateTimeFormat.forPattern(DATETIME_FORMAT_LOCAL_DATE);
			DateTime eventDateObj = savedDateFormat.parseDateTime(eventDate);
			stringToReturn.append(String.format(TO_STRING_DATE, eventDateObj.toString(DATETIME_FORMAT_TO_STRING_DATE)));
		}
		if (startTime != null) {
			stringToReturn.append(String.format(TO_STRING_START_TIME, startTime));
		}
		if (endTime != null) {
			stringToReturn.append(String.format(TO_STRING_END_TIME, endTime));
		}
		return stringToReturn.toString();
	}
	
	/**
	 * Returns the string with only the name of the event for display
	 * @return only name string
	 */
	public String onlyNameString() {
		StringBuilder stringToReturn = new StringBuilder();
		stringToReturn.append(String.format(TO_STRING_EVENT_NAME, eventName));
		if (isDone) stringToReturn.append(TO_STRING_IS_DONE);
		else stringToReturn.append(TO_STRING_IS_NOT_DONE);
		
		return stringToReturn.toString();
	}
	
	@Override
	public boolean equals(Object obj) {
		if (obj == this) {
            return true;
        }
        if (obj == null || obj.getClass() != this.getClass()) {
            return false;
        }
        
        ScheduleEvent otherEvent = (ScheduleEvent) obj;
        
        if (!this.eventName.equals(otherEvent.eventName)) return false;
        if (!this.eventDate.equals(otherEvent.eventDate)) return false;
        if (!(STRING_EMPTY+this.isDone).equals(STRING_EMPTY+otherEvent.isDone)) return false;
        if (!this.UID.equals(otherEvent.UID)) return false;
        if (!this.googleUID.equals(otherEvent.googleUID)) return false;
        
        if (eventType == EVENT_TYPE_TIMED) {
        	if (!this.startTime.equals(otherEvent.startTime)) return false;
        	if (!this.endTime.equals(otherEvent.endTime)) return false;
        }
        if (eventType == EVENT_TYPE_DEADLINE) {
        	if (!this.endTime.equals(otherEvent.endTime)) return false;
        }
        
        return true;
	}
	
	/**
	 * Returns the JSON version of the event for saving to local database
	 * @return toSave - JSON version of the event
	 */
	public String toSave() {
		StringBuilder toSave = new StringBuilder();
		toSave.append(String.format(TO_SAVE_JSON_EVENT_NAME, eventName));
		toSave.append(String.format(TO_SAVE_JSON_EVENT_TYPE, eventType));
		toSave.append(String.format(TO_SAVE_JSON_EVENT_DATE, eventDate));
		toSave.append(String.format(TO_SAVE_JSON_EVENT_UPDATED, updated));
		if (startTime != null) {
			toSave.append(String.format(TO_SAVE_JSON_EVENT_START, startTime));
		}
		if (endTime != null) {
			toSave.append(String.format(TO_SAVE_JSON_EVENT_END, endTime));
		}
		if (UID != null) {
			toSave.append(String.format(TO_SAVE_JSON_EVENT_UID, UID));
		}
		if (googleUID != null) {
			toSave.append(String.format(TO_SAVE_JSON_EVENT_GOOGLEID, googleUID));
		}
		toSave.append(String.format(TO_SAVE_JSON_EVENT_ISDONE, isDone));
		return toSave.toString();
	}
	
	/**
	 * Returns the JSON version of the event for uploading to Google Calendar
	 * @return jsonGoogleCalendarItem - JSON version of the event for GCal & GTask
	 */
	public String toJSON() {
		StringBuilder jsonGoogleCalendarItem = new StringBuilder();
		if (eventType == EVENT_TYPE_TIMED) {
			createTimedEventJsonItem(jsonGoogleCalendarItem);
		} else if (eventType == EVENT_TYPE_DEADLINE) {
			createDeadlineEventJsonItem(jsonGoogleCalendarItem);
		} else if (eventType == EVENT_TYPE_FLOATING){
			createFloatingEventJsonItem(jsonGoogleCalendarItem);
		} else {
			//TODO log error
			return null;
		}
		return jsonGoogleCalendarItem.toString();
	}

	/**
	 * Appends a Floating Event Json Item for Google Calendar to the input jsonGoogleCalendarItem
	 * @param jsonGoogleCalendarItem - StringBuilder
	 */
	private void createFloatingEventJsonItem(StringBuilder jsonGoogleCalendarItem) {
		jsonGoogleCalendarItem.append(TO_JSON_STARTING_BRACE);
		jsonGoogleCalendarItem.append(String.format(TO_JSON_FLOATING_EVENT_DETAILS, eventName, UID, this.getStatus()));
		if (googleUID != null) {
			jsonGoogleCalendarItem.append(String.format(TO_JSON_EVENT_GOOGLEID, googleUID));
		}
		jsonGoogleCalendarItem.append(TO_JSON_ENDING_BRACE);
	}

	/**
	 * Appends a Deadline Event Json Item for Google Calendar to the input jsonGoogleCalendarItem
	 * @param jsonGoogleCalendarItem - StringBuilder
	 */
	private void createDeadlineEventJsonItem(StringBuilder jsonGoogleCalendarItem) {
		DateTimeFormatter formatter = DateTimeFormat.forPattern(DATETIME_FORMAT_LOCAL_DATE);
		DateTimeFormatter dateFormatter = DateTimeFormat.forPattern(DATETIME_FORMAT_GTASK_ITEM_DATETIME);
		DateTime eventDateObject = formatter.parseDateTime(eventDate);

		jsonGoogleCalendarItem.append(TO_JSON_STARTING_BRACE);
		String eventNameToCreate = eventName;
		if (endTime != null) {
			eventNameToCreate = String.format(TO_JSON_DEADLINE_EVENT_NAME, endTime, eventName);
		}
		jsonGoogleCalendarItem.append(String.format(GOOGLE_TASK_ITEM_JSON_WITH_DATE, eventNameToCreate, dateFormatter.print(eventDateObject), UID, this.getStatus()));
		if (googleUID != null) {
			jsonGoogleCalendarItem.append(String.format(TO_JSON_EVENT_GOOGLEID, googleUID));
		}
		jsonGoogleCalendarItem.append(TO_JSON_ENDING_BRACE);
	}

	/**
	 * Appends a Timed Event Json Item for Google Calendar to the input jsonGoogleCalendarItem
	 * @param jsonGoogleCalendarItem - StringBuilder
	 */
	private void createTimedEventJsonItem(StringBuilder jsonGoogleCalendarItem) {
		jsonGoogleCalendarItem.append(TO_JSON_STARTING_BRACE);
		jsonGoogleCalendarItem.append(String.format(TO_JSON_TIMED_EVENT_SUMMARY, eventName));
		jsonGoogleCalendarItem.append(String.format(TO_JSON_TIMED_EVENT_EXTENDED_ITEMS, isDone, UID, eventType));
		jsonGoogleCalendarItem.append(String.format(TO_JSON_TIMED_EVENT_START, createStartEndTimeEntry(CREATE_TIME_ENTRY_TYPE_START)));
		jsonGoogleCalendarItem.append(String.format(TO_JSON_TIMED_EVENT_END, createStartEndTimeEntry(CREATE_TIME_ENTRY_TYPE_END)));
		if (googleUID != null) {
			jsonGoogleCalendarItem.append(String.format(TO_JSON_EVENT_GOOGLEID, googleUID));
		}
		jsonGoogleCalendarItem.append(TO_JSON_ENDING_BRACE);
	}
	
	/**
	 * Returns a start or end time entry in a dateTime format
	 * @param startOrEnd
	 * @return jsonStartEndItem - String in DateTime format
	 */
	private String createStartEndTimeEntry(String startOrEnd) {
		StringBuilder jsonStartEndItem = new StringBuilder();
		DateTimeFormatter localDateFormatter = DateTimeFormat.forPattern(DATETIME_FORMAT_LOCAL_DATETIME);
    	DateTimeFormatter googleDateTimeFormatter = DateTimeFormat.forPattern(DATETIME_FORMAT_GCAL_ITEM_DATETIME);
    	DateTime dateTimeToReturn = null;
    	
		jsonStartEndItem.append(String.format(CREATE_TIME_ENTRY_OPENING, startOrEnd));
		if (startOrEnd.equals(CREATE_TIME_ENTRY_TYPE_START)) {
			dateTimeToReturn = localDateFormatter.parseDateTime(String.format(CREATE_TIME_DATE_TIME_ENTRY, eventDate, startTime));
		} else {
			dateTimeToReturn = localDateFormatter.parseDateTime(String.format(CREATE_TIME_DATE_TIME_ENTRY, eventDate, endTime));
		}
		jsonStartEndItem.append(googleDateTimeFormatter.print(dateTimeToReturn));
	
		jsonStartEndItem.append(CREATE_TIME_ENTRY_CLOSING);	
		return jsonStartEndItem.toString();
	}
	
	/**
	 * Returns the status of the task in the format of Google Tasks
	 * @return needsAction or completed
	 */
	private String getStatus() {
		if (this.isDone) return GOOGLE_TASKS_COMPLETED;
		else return GOOGLE_TASKS_NEEDS_ACTION;
	}
}
